﻿#region Copyright and License Notice
// Copyright (C)2010-2016 - Rob Levine and other contributors
// http://configgen.codeplex.com
// 
// This file is part of ConfigGen.
// 
// ConfigGen is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// ConfigGen is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License and 
// the GNU Lesser General Public License along with ConfigGen.  
// If not, see <http://www.gnu.org/licenses/>
#endregion

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using ConfigGen.Core.ConfigurationSettings;
using ConfigGen.Utilities.Extensions.System;

namespace ConfigGen.Core.TemplateProcessing
{
    /// <summary>
    /// Node processor for processing "Appy" element.
    /// </summary>
    public class ApplyWhenElementProcessor : ConditionalElementProcessorBase
    {
        private readonly ApplyElementCreator _applyElementCreator = new ApplyElementCreator();

        /// <summary>
        /// Processes the supplied ApplyWhen element.
        /// </summary>
        /// <param name="element">An Apply element.</param>
        /// <param name="machineConfigurationSettings">The machine settings.</param>
        /// <param name="configurationExpressionEvaluator">The configuration settings search helper.</param>
        public override void ProcessNode(XElement element, MachineConfigurationSettings machineConfigurationSettings, IConfigurationExpressionEvaluator configurationExpressionEvaluator)
        {
            if (element == null) throw new ArgumentNullException("element");
            if (machineConfigurationSettings == null) throw new ArgumentNullException("machineConfigurationSettings");
            if (configurationExpressionEvaluator == null) throw new ArgumentNullException("configurationExpressionEvaluator");

            if (element.Name.LocalName != "Apply"
               || element.Name.Namespace != TemplateProcessor.ConfigGenXmlNamespace)
            {
                throw new ArgumentException("Supplied element should have name 'Apply' in the configgen namespace.", "element");
            }

            var applyElement = _applyElementCreator.Create(element);

            bool trueConditionAlreadyEvaluated = false;
            while (applyElement.PredicateSubNodes.Count > 0)
            {
                var subNode = applyElement.PredicateSubNodes.Dequeue();
                var conditionResult = EvaluateCondition(subNode.Element, machineConfigurationSettings, configurationExpressionEvaluator);

                ProcessElement(subNode, conditionResult && !trueConditionAlreadyEvaluated);
              
                trueConditionAlreadyEvaluated |= conditionResult;
            }

            if (applyElement.FinalElseSubNode != null)
            {
                ProcessElement(applyElement.FinalElseSubNode, !trueConditionAlreadyEvaluated);
            }

            element.Remove();
        }

        private void ProcessElement(ApplyElementSubNode applyElementSubNode, bool result)
        {
            if (result)
            {
                foreach (var child in new Queue<XNode>(applyElementSubNode.Element.Nodes()))
                {
                    child.Remove();
                    applyElementSubNode.ParentApplyElement.AddBeforeSelf(child);
                }
            }
            else
            {
                switch (applyElementSubNode.OnNotAppliedAction)
                {
                    case OnNotAppliedAction.Remove:
                        applyElementSubNode.Element.Remove();
                        break;
                    case OnNotAppliedAction.CommentOut:
                        using (var ms = new MemoryStream())
                        using (var writer = new XmlTextWriter(ms, Encoding.UTF8))
                        using (var reader = new StreamReader(ms))
                        {
                            foreach (var child in new Queue<XNode>(applyElementSubNode.Element.Nodes()))
                            {
                                child.WriteTo(writer);
                                child.Remove();
                            }
                            writer.Flush();
                            ms.Position = 0;

                            var commentedOutNode = new XComment(reader.ReadToEnd().Trim());
                            applyElementSubNode.ParentApplyElement.AddBeforeSelf(commentedOutNode);

                            if (!applyElementSubNode.OnCommentedOutComment.IsNullOrEmpty())
                            {
                                var commentForCommentedOutNode = new XComment(" " + applyElementSubNode.OnCommentedOutComment + " ");
                                commentedOutNode.AddBeforeSelf(commentForCommentedOutNode);
                                commentForCommentedOutNode.AddAfterSelf(new XText(Environment.NewLine));
                                commentedOutNode.AddAfterSelf(new XText(Environment.NewLine));
                                commentedOutNode.AddAfterSelf(new XText(Environment.NewLine));
                            }
                        }
                        break;
                    default:
                        throw new NotSupportedException("OnNotAppliedAction not supported: " +
                                                        applyElementSubNode.OnNotAppliedAction);
                }
            }
        }

        /// <summary>
        /// Gets the name of the condition attribute, which contains the conditional request.
        /// </summary>
        protected override string ConditionAttributeName
        {
            get { return "condition"; }
        }
    }
}
